/*=============================================================================
  DevBuffer.cpp : Direct3D9 vertex/index buffers management.
  Copyright (c) 2001-2009 Crytek Studios. All Rights Reserved.

  Revision history:
    * Created by Honich Andrey

=============================================================================*/

#include "StdAfx.h"
#include "DriverD3D.h"

//===============================================================================

#if defined(DIRECT3D10)
void CDevBufferMan::LockDevMan()
{
	m_Lock.Lock();
}

void CDevBufferMan::UnlockDevMan()
{
	m_Lock.Unlock();
}
#endif

SDevBuffer::~SDevBuffer()
{
}

void CDevBufferMan::Init()
{
 m_activeVBs = 0; 
 m_activeIBs = 0; 
#ifdef DIRECT3D10
  m_nMaxStagingBuffers = CD3D9Renderer::CV_d3d10_NumStagingBuffers;

  for (int j=0; j<m_nMaxStagingBuffers; j++)
  {
    DynamicVB<byte> *pVB = new DynamicVB<byte>(gcpRendD3D->m_pd3dDevice, gcpRendD3D->CV_d3d9_vbpoolsize, true);
    m_pVBTemp[j] = pVB;
    pVB->CreateVB(gcpRendD3D->m_pd3dDevice, gcpRendD3D->CV_d3d9_vbpoolsize, 1);
    m_pIBTemp[j] = new DynamicIB<uint16>(gcpRendD3D->m_pd3dDevice, gcpRendD3D->CV_d3d9_ibpoolsize, true);
  }
#endif
}

void CDevBufferMan::Update()
{
#if BUFFER_NO_LOCK_ON_UPDATE
	//Update must happen on renderthread delete list is not thread safe
	assert(gRenDev->m_pRT->IsRenderThread());

	{
		std::vector<delayDeleteInfo>::iterator deleteIter = m_delayDeleteList.begin();

		while(deleteIter != m_delayDeleteList.end())
		{
			if( gEnv->pRenderer->GetFrameID(false)-deleteIter->deleteFrame > DELAY_DELETE_NUM_FRAMES )
			{
				//fprintf(stdout, "Delay Delete Entry, Pool:%d [%d], Frame:%d, size:%d\n", deleteIter->poolItem.m_nPool, deleteIter->poolItem.m_nBufOffset, gEnv->pRenderer->GetFrameID(false), deleteIter->size);

				SDevPool& Pool = deleteIter->poolType == POOL_VERTEX ? m_VBPools[deleteIter->poolItem.m_nPool] : m_IBPools[deleteIter->poolItem.m_nPool];
				Pool.m_nAllocatedSize -= deleteIter->size;

				Pool.m_AllocTree.Free(deleteIter->poolItem.m_allocHdl);

				deleteIter = m_delayDeleteList.erase(deleteIter);
			}
			else
			{
				++deleteIter;
			}
		}
	}
#endif
}

void CDevBufferMan::Invalidate()
{
}

int32 CDevBufferMan::CreateVB(int32 nSize)
{
	AUTO_LOCK(m_Lock);
  SDevBuffer DB;
  int32 nVB = -1;

  if (CD3D9Renderer::CV_d3d9_vbpools && nSize<POOL_SIZE_VB)
    nVB = CreateDevVBInPool(nSize, DB);
  else
    nVB = CreateDevVB(nSize, DB);

  ValidateVBPools();

  return nVB;
}

int32 CDevBufferMan::CreateIB(int32 nSize)
{
	AUTO_LOCK(m_Lock);
  SDevBuffer DB;
  int32 nIB = -1;

  if (CD3D9Renderer::CV_d3d9_ibpools && nSize<POOL_SIZE_IB)
    nIB = CreateDevIBInPool(nSize, DB);
  else
    nIB = CreateDevIB(nSize, DB);

  ValidateIBPools();

  return nIB;
}

bool CDevBufferMan::ReleaseVB(int32 nVB)
{
	AUTO_LOCK(m_Lock);
  assert(nVB < (int32)m_VBs.size());
  if (nVB >= (int32)m_VBs.size())
    return false;

  SDevBuffer &DB = m_VBs[nVB];
  assert(DB.m_nSize > 0);
  if (DB.m_pPoolItem)
    ReleaseDevVBInPool(DB);
  else
    ReleaseDevVB(DB);
  m_FreeVBSlots.push_back(nVB);
  --m_activeVBs; 
  DB.m_nSize = 0;

  ValidateVBPools();

  return true;
}

bool CDevBufferMan::ReleaseIB(int32 nIB)
{
	AUTO_LOCK(m_Lock);
  assert(nIB < (int32)m_IBs.size());
  if (nIB >= (int32)m_IBs.size())
    return false;

  SDevBuffer &DB = m_IBs[nIB];
  assert(DB.m_nSize > 0);
  if (DB.m_pPoolItem)
    ReleaseDevIBInPool(DB);
  else
    ReleaseDevIB(DB);
  m_FreeIBSlots.push_back(nIB);
  --m_activeIBs; 
  DB.m_nSize = 0;

  ValidateIBPools();

  return true;
}

D3DVertexBuffer *CDevBufferMan::GetD3DVB(int32 nVB, int32* nOffs)
{
	FUNCTION_PROFILER_RENDER_FLAT
	SDevBuffer *pDB = GetDevVB(nVB);
	assert(pDB);
	if (pDB)
	{
		if (pDB->m_pPoolItem)
			*nOffs = pDB->m_pPoolItem->m_nBufOffset;
		else
			*nOffs = 0;
		return pDB->m_D3DBuf.m_pVB;
	}
	return NULL;
}

D3DIndexBuffer  *CDevBufferMan::GetD3DIB(int32 nIB, int32* nOffs)
{
	FUNCTION_PROFILER_RENDER_FLAT
	SDevBuffer *pDB = GetDevIB(nIB);
	assert(pDB);
	if (pDB)
	{
		if (pDB->m_pPoolItem)
			*nOffs = pDB->m_pPoolItem->m_nBufOffset;
		else
			*nOffs = 0;
		return pDB->m_D3DBuf.m_pIB;
	}
	*nOffs = 0;
	return NULL;
}

void *CDevBufferMan::UpdateVB(int32 nVB, const void *pData, int32 nSize, int32 nOffset, bool stall)
{
  assert(pData);
	
	//Don't sync with GPU, allocate a new block of mem, and delay free the old chunk
#if BUFFER_NO_LOCK_ON_UPDATE
	if( CD3D9Renderer::CV_d3d9_vbpools )
	{
		assert(gRenDev->m_pRT->IsRenderThread());

		SDevBuffer *pDB = GetDevVB(nVB);

		//check stream can be allocated using the pool
		if(pDB->m_pPoolItem)
		{
			SDevPoolItem newPoolItem;
			int poolIndex;

			//full update - lockless
			if(nOffset==0 && nSize==pDB->m_nSize)
			{
				if( PoolAllocateChunkVB(nSize, &newPoolItem, poolIndex) )
				{
					//If we have already allocated from the pool, add it do delay delete list
					if(pDB->m_pPoolItem->m_nPool != -1)
					{
						delayDeleteInfo d;
						d.poolItem = *pDB->m_pPoolItem;
						d.deleteFrame = gEnv->pRenderer->GetFrameID(false);
						d.size = pDB->m_nSize;
						d.poolType = POOL_VERTEX; 

						m_delayDeleteList.push_back(d);
					}

					//Update the dev buffer
					pDB->m_pPoolItem->m_nBufOffset = newPoolItem.m_nBufOffset; 
					pDB->m_pPoolItem->m_allocHdl = newPoolItem.m_allocHdl;
					pDB->m_pPoolItem->m_nPool = poolIndex;
					pDB->m_D3DBuf.m_pVB = m_VBPools[poolIndex].m_D3DBuf.m_pVB;

					assert((int)m_VBPools[poolIndex].pBasePtr!=0xffffffff);

					//No Lock!
					byte *pDst = m_VBPools[poolIndex].pBasePtr + pDB->m_pPoolItem->m_nBufOffset;











					//copy into new memory
					cryMemcpy(pDst, pData, nSize, MC_CPU_TO_GPU);

					//clear CPU cache
					byte* flushAddr = (byte*)((int)pDst & ~127); //first cache line
					int numCacheLines = (((nSize+127) & ~127) >> 7) + 1; //size/128 + 1

					//prevent flushing out of range of VB












					//gcpRendD3D->m_pd3dDevice->InvalidateResourceGpuCache(pDB->m_D3DBuf.m_pVB, 0);

					return pDst;
				}
				else if(pDB->m_pPoolItem->m_nPool == -1)
				{
					//we have run out of memory, 
					//can't fallback to sync lock as this is the first alloc for this VB
					gEnv->bIsOutOfMemory=true;
					CryFatalError("OUT OF MEMORY. Failed to allocate mem for Vertex Buffer");
					return NULL;
				}
			}
			else if(pDB->m_pPoolItem->m_nPool == -1) //partial update, make sure mem is allocated
			{
				if( PoolAllocateChunkVB(pDB->m_nSize, &newPoolItem, poolIndex) )
				{
					pDB->m_pPoolItem->m_nBufOffset = newPoolItem.m_nBufOffset; 
					pDB->m_pPoolItem->m_allocHdl = newPoolItem.m_allocHdl; 
					pDB->m_pPoolItem->m_nPool = poolIndex;
					pDB->m_D3DBuf.m_pVB = m_VBPools[poolIndex].m_D3DBuf.m_pVB;
				}
				else
				{
					gEnv->bIsOutOfMemory=true;
					CryFatalError("OUT OF MEMORY. Failed to allocate mem for Vertex Buffer");
					return NULL;
				}
			}
		}
	}
#endif

	byte *pDst = (byte *)LockVB(nVB, nOffset, nSize, FSL_WRITE | (stall?0:FSL_NONSTALL_MAP));
  assert(pDst);
  if (!pDst)
    return NULL;

  cryMemcpy(pDst, pData, nSize, MC_CPU_TO_GPU);

  UnlockVB(nVB);

  return pDst;
}
void *CDevBufferMan::UpdateIB(int32 nIB, const void *pData, int32 nSize, int32 nOffset, bool stall)
{
  assert(pData);
	
#if BUFFER_NO_LOCK_ON_UPDATE
	if( CD3D9Renderer::CV_d3d9_ibpools )
	{
		assert(gRenDev->m_pRT->IsRenderThread());

		SDevBuffer *pDB = GetDevIB(nIB);

		//check stream can be allocated using the pool
		if(pDB->m_pPoolItem)
		{
			SDevPoolItem newPoolItem;
			int poolIndex;

			//full update - lockless
			if(nOffset==0 && nSize==pDB->m_nSize)
			{
				if( PoolAllocateChunkIB(nSize, &newPoolItem, poolIndex) )
				{
					//If have already allocated from the pool, add it do delay delete list
					if(pDB->m_pPoolItem->m_nPool != -1)
					{
						delayDeleteInfo d;
						d.poolItem = *pDB->m_pPoolItem;
						d.deleteFrame = gEnv->pRenderer->GetFrameID(false);
						d.size = pDB->m_nSize;
						d.poolType = POOL_INDEX; 

						m_delayDeleteList.push_back(d);
					}

					//fprintf(stdout, "Add Delete Entry IB, Pool:%d [%d], Frame:%d, size:%d\n", d.poolItem.m_nPool, d.poolItem.m_nBufOffset, d.deleteFrame, d.size);

					//Update the dev buffer
					pDB->m_pPoolItem->m_nBufOffset = newPoolItem.m_nBufOffset; 
					pDB->m_pPoolItem->m_allocHdl = newPoolItem.m_allocHdl; 
					pDB->m_pPoolItem->m_nPool = poolIndex;
					pDB->m_D3DBuf.m_pIB = m_IBPools[poolIndex].m_D3DBuf.m_pIB;

					assert((int)m_IBPools[poolIndex].pBasePtr!=0xffffffff);

					//No Lock!
					byte *pDst = m_IBPools[poolIndex].pBasePtr + pDB->m_pPoolItem->m_nBufOffset;









					//copy into new memory
					cryMemcpy(pDst, pData, nSize, MC_CPU_TO_GPU);

					//clear CPU cache

















					return pDst;
				}
				else if(pDB->m_pPoolItem->m_nPool == -1)
				{
					//can't fallback to sync lock as this is the first alloc for this IB
					gEnv->bIsOutOfMemory=true;
					CryFatalError("OUT OF MEMORY. Failed to allocate mem for Index Buffer");
					return NULL;
				}
			}
			else if(pDB->m_pPoolItem->m_nPool == -1) //partial update, make sure mem is allocated
			{
				if( PoolAllocateChunkIB(pDB->m_nSize, &newPoolItem, poolIndex) )
				{
					pDB->m_pPoolItem->m_nBufOffset = newPoolItem.m_nBufOffset; 
					pDB->m_pPoolItem->m_allocHdl = newPoolItem.m_allocHdl; 
					pDB->m_pPoolItem->m_nPool = poolIndex;
					pDB->m_D3DBuf.m_pIB = m_IBPools[poolIndex].m_D3DBuf.m_pIB;
				}
				else
				{
					gEnv->bIsOutOfMemory=true;
					CryFatalError("OUT OF MEMORY. Failed to allocate mem for Index Buffer");
					return NULL;
				}
			}
		}
	}
#endif

	
  byte *pDst = (byte *)LockIB(nIB, nOffset, nSize, FSL_WRITE | (stall?0:FSL_NONSTALL_MAP));
  assert(pDst);
  if (!pDst)
    return NULL;

  cryMemcpy(pDst, pData, nSize, MC_CPU_TO_GPU);

  UnlockIB(nIB);

  return pDst;
}

void *CDevBufferMan::LockVB(D3DVertexBuffer *pVB, int32 nOffset, uint32 nFlags)
{
  byte *pDst = NULL;
  HRESULT hr = S_OK;
#if defined (DIRECT3D9)
  D3DVERTEXBUFFER_DESC Desc;
  pVB->GetDesc(&Desc);





  int nDevSize = Desc.Size - nOffset;
  hr = pVB->Lock(nOffset, nDevSize, (void **)&pDst, 0);

#elif defined (DIRECT3D10)
  D3D11_BUFFER_DESC Desc;
  pVB->GetDesc(&Desc);
  int nDevSize = Desc.ByteWidth - nOffset;
  // Get temporary vertex buffer
  DynamicVB<byte> *pTempBuf = static_cast<DynamicVB<byte> *>(m_pVBTemp[m_nCurStagedVB]);
  if (nDevSize > pTempBuf->GetBytesCount())
  {
    assert(0);
    return NULL;
  }
  else
  {
    uint32 i=0;
    for (; i<m_nMaxStagingBuffers; ++i) 
    { 
      if (nDevSize+pTempBuf->GetOffset() > pTempBuf->GetBytesCount())
      {
        m_nCurStagedVB++;
        if (m_nCurStagedVB > m_nMaxStagingBuffers-1)
          m_nCurStagedVB = 0;
        pTempBuf = static_cast<DynamicVB<byte> *>(m_pVBTemp[m_nCurStagedVB]);
      }
      else 
        break; 
    }
    if (i==m_nMaxStagingBuffers) 
    { 
      return NULL;
    } 
  }
  uint32 nOffsTemp = 0;
  if (nFlags & FSL_READ)
  {
    int nOffs = pTempBuf->GetOffset();
    if (nOffs + nDevSize > pTempBuf->GetBytesCount())
      nOffs = 0;
    D3D11_BOX box;
    ZeroStruct(box);
    box.left = nOffset;
    box.right = nOffset + nDevSize;
    box.bottom = 1;
    box.back = 1;
    gcpRendD3D->m_pd3dDeviceContext->CopySubresourceRegion(pTempBuf->m_pVB, 0, nOffs, 0, 0, pVB, 0, &box);
  }
  pDst = pTempBuf->LockVB(nDevSize, nOffsTemp, (nFlags & FSL_WRITE) != 0, (nFlags & FSL_NONSTALL_MAP) != 0);
#endif
  assert(hr == S_OK);
  return pDst;
}
void CDevBufferMan::UnlockVB(D3DVertexBuffer *pVB)
{
  byte *pDst = NULL;
  HRESULT hr = S_OK;
#if defined (DIRECT3D9)
  hr = pVB->Unlock();
#elif defined (DIRECT3D10)
  DynamicVB<byte> *pTempBuf = static_cast<DynamicVB<byte> *>(m_pVBTemp[m_nCurStagedVB]);
  pTempBuf->UnlockVB();
#endif
  assert(hr == S_OK);
}

void *CDevBufferMan::LockIB(D3DIndexBuffer *pIB, int32 nOffset, uint32 nFlags)
{
  uint16 *pDst = NULL;
  HRESULT hr = S_OK;
#if defined (DIRECT3D9)
  D3DINDEXBUFFER_DESC Desc;
  pIB->GetDesc(&Desc);





  int nDevSize = Desc.Size - (nOffset*2);
  hr = pIB->Lock(nOffset, nDevSize, (void **)&pDst, 0);

#elif defined (DIRECT3D10)
  D3D11_BUFFER_DESC Desc;
  pIB->GetDesc(&Desc);
  int nInds = (Desc.ByteWidth>>1) - nOffset;
  // Get temporary vertex buffer
  DynamicIB<uint16> *pTempBuf = static_cast<DynamicIB<uint16> *>(m_pIBTemp[m_nCurStagedIB]);
  if (nInds > pTempBuf->GetCount())
  {
    assert(0);
    return NULL;
  }
  else
  {
    uint32 i=0; 
    for (; i<m_nMaxStagingBuffers; ++i)
    {
      if (nInds+pTempBuf->GetOffset() > pTempBuf->GetCount())
      {
        m_nCurStagedIB++;
        if (m_nCurStagedIB > m_nMaxStagingBuffers-1)
          m_nCurStagedIB = 0;
        pTempBuf = static_cast<DynamicIB<uint16> *>(m_pIBTemp[m_nCurStagedIB]);      
      } else
        break; 
    }
    if (i==m_nMaxStagingBuffers) 
    { 
			 return NULL;
    } 
  }

  uint32 nOffsTemp = 0;
  if (nFlags & FSL_READ)
  {
    int nOffs = pTempBuf->GetOffset();
    if (nOffs + nInds > pTempBuf->GetCount())
      nOffs = 0;
    D3D11_BOX box;
    ZeroStruct(box);
    box.left = nOffset*2;
    box.right = (nOffset + nInds)*2;
    box.bottom = 1;
    box.back = 1;
    gcpRendD3D->m_pd3dDeviceContext->CopySubresourceRegion(pTempBuf->GetInterface(), 0, nOffs*2, 0, 0, pIB, 0, &box);
  }
  pDst = pTempBuf->LockIB(nInds, nOffsTemp, (nFlags & FSL_WRITE) != 0, (nFlags & FSL_NONSTALL_MAP) != 0);
#endif
  assert(hr == S_OK);
  return pDst;
}
void CDevBufferMan::UnlockIB(D3DIndexBuffer *pIB)
{
  byte *pDst = NULL;
  HRESULT hr = S_OK;
#if defined (DIRECT3D9)
  hr = pIB->Unlock();
#elif defined (DIRECT3D10)
  DynamicIB<uint16> *pTempBuf = static_cast<DynamicIB<uint16> *>(m_pIBTemp[m_nCurStagedIB]);
  pTempBuf->UnlockIB();
#endif
  assert(hr == S_OK);
}

void *CDevBufferMan::LockVB(int32 nVB, int32 nOffset, int32 nSize, uint32 nFlags)
{
  int32 nDevOffs = 0;
	SDevBuffer *pDB = GetDevVB(nVB);
  D3DVertexBuffer *pVB = GetD3DVB(nVB, &nDevOffs);
  nDevOffs += nOffset;


# if BUFFER_ENABLE_DIRECT_ACCESS
	if (nFlags & FSL_DIRECT) 
	{
		byte* basePtr = (byte*)(pDB->m_pPoolItem ? m_VBPools[pDB->m_pPoolItem->m_nPool].pBasePtr : pDB->pBasePtr);
		if (!basePtr) __debugbreak();
		return basePtr + nDevOffs;
	}
# endif 

  byte *pDst = NULL;
  HRESULT hr = S_OK;
#if defined (DIRECT3D9)







	#if (defined(WIN32) || defined(WIN64))
	assert(pDB);
	if (pDB)
  #endif	// #if (defined(WIN32) || defined(WIN64))
	{
		int32 nDevSize = pDB->m_nSize;
		if (nSize && nSize < nDevSize)
			nDevSize = nSize;
		hr = pVB->Lock(nDevOffs, nDevSize, (void **)&pDst, 0);
	}


#elif defined (DIRECT3D10)
#if defined(USE_VBIB_PUSH_DOWN)
	if (nFlags & (FSL_READ))
	{	
		// in push down mode, return the original data ptr directly, instead of a staged buffer
		return pVB->RawData();
	}
#endif
	IF(!pDB,0)
		return NULL;
  int32 nDevSize = pDB->m_nSize;
  if (nSize && nSize < nDevSize)
    nDevSize = nSize;
  // Get temporary vertex buffer
  DynamicVB<byte> *pTempBuf = static_cast<DynamicVB<byte> *>(m_pVBTemp[m_nCurStagedVB]);
  pDB->m_bTempBuf = false;
  if (nDevSize > pTempBuf->GetBytesCount())
  {
    pDB->m_bTempBuf = true;
    pTempBuf = new DynamicVB<byte>(gcpRendD3D->m_pd3dDevice, nDevSize, true);
    hr = pTempBuf->CreateVB(gcpRendD3D->m_pd3dDevice, nDevSize, 1);
		CHECK_HRESULT(hr);
		IF(!SUCCEEDED(hr),0)
		{
			delete pTempBuf;
			return NULL;
		}
  }
  else
  {
    uint32 i=0; 
    for (; i<m_nMaxStagingBuffers; ++i)
    {
      if (nDevSize+pTempBuf->GetOffset() > pTempBuf->GetBytesCount())
      {
        m_nCurStagedVB++;
        if (m_nCurStagedVB > m_nMaxStagingBuffers-1)
          m_nCurStagedVB = 0;
        pTempBuf = static_cast<DynamicVB<byte> *>(m_pVBTemp[m_nCurStagedVB]);
      }
      else 
        break;
    }
    if (i==m_nMaxStagingBuffers)
    {
      pDB->m_bTempBuf = true;
      pTempBuf = new DynamicVB<byte>(gcpRendD3D->m_pd3dDevice, nDevSize, true);
      hr = pTempBuf->CreateVB(gcpRendD3D->m_pd3dDevice, nDevSize, 1);
			CHECK_HRESULT(hr);
		  IF(!SUCCEEDED(hr),0)
		  {
			  delete pTempBuf;
			  return NULL;
		  }
    }
  }
  pDB->m_pStagedVB = pTempBuf;
  pDB->m_nLockFlags = nFlags;
  uint32 nOffsTemp = 0;
  if (nFlags & FSL_READ)
  {
    int nOffs = pTempBuf->GetOffset();
    if (nOffs + nDevSize > pTempBuf->GetBytesCount())
      nOffs = 0;
    D3D11_BOX box;
    ZeroStruct(box);
    box.left = nDevOffs;
    box.right = nDevOffs + nDevSize;
    box.bottom = 1;
    box.back = 1;
    gcpRendD3D->m_pd3dDeviceContext->CopySubresourceRegion(pTempBuf->m_pVB, 0, nOffs, 0, 0, pVB, 0, &box);
  }
  pDst = pTempBuf->LockVB(nDevSize, nOffsTemp, true,(nFlags & FSL_NONSTALL_MAP) != 0);
  pDB->m_nStagedOffset = nOffsTemp;
  pDB->m_nDevOffset = nDevOffs;
  pDB->m_nStagedSize = nDevSize;
#endif
	CHECK_HRESULT(hr);
  return pDst;
}

void CDevBufferMan::UnlockVB(int32 nVB)
{
	HRESULT hr = S_OK;
  int32 nDevOffs = 0;
  D3DVertexBuffer *pVB = GetD3DVB(nVB, &nDevOffs);
  assert(pVB);
  if (!pVB)
    return;

#if defined (DIRECT3D9)
  pVB->Unlock();
#elif defined (DIRECT3D10)
  SDevBuffer *pDB = GetDevVB(nVB);
  DynamicVB<byte> *pStagedVB = static_cast<DynamicVB<byte> *>(pDB->m_pStagedVB);
  assert(pStagedVB);
  if (!pStagedVB)
    return;
  pStagedVB->UnlockVB();
  if (pDB->m_nLockFlags & FSL_WRITE)
  {
    D3D11_BOX box;
    ZeroStruct(box);
    box.left = pDB->m_nStagedOffset;
    box.right = pDB->m_nStagedSize+box.left;
    box.bottom = 1;
    box.back = 1;
    gcpRendD3D->m_pd3dDeviceContext->CopySubresourceRegion(pVB, 0, pDB->m_nDevOffset, 0, 0, pStagedVB->m_pVB, 0, &box);
  }
  if (pDB->m_bTempBuf)
  {
    pDB->m_bTempBuf = false;
    SAFE_DELETE(pStagedVB);
  }
	CHECK_HRESULT(hr);
  pDB->m_pStagedVB = NULL;
#endif
}

void *CDevBufferMan::LockIB(int32 nIB, int32 nOffset, int32 nSize, uint32 nFlags)
{
  int32 nDevOffs = 0;
  SDevBuffer *pDB = GetDevIB(nIB);
  D3DIndexBuffer *pIB = GetD3DIB(nIB, &nDevOffs);
  assert (pIB);
  if (!pIB)
    return NULL;

  nDevOffs += nOffset;

# if BUFFER_ENABLE_DIRECT_ACCESS
	if (nFlags & FSL_DIRECT) 
	{
		byte* basePtr = (byte*)(pDB->m_pPoolItem ? m_IBPools[pDB->m_pPoolItem->m_nPool].pBasePtr : pDB->pBasePtr);
		if (!basePtr) __debugbreak();
		return basePtr + nDevOffs;
	}
# endif 

  byte *pDst = NULL;
  HRESULT hr = S_OK;
#if defined (DIRECT3D9)




  int32 nDevSize = pDB->m_nSize;
  if (nSize && nSize < nDevSize)
    nDevSize = nSize;
  hr = pIB->Lock(nDevOffs, nDevSize, (void **)&pDst, 0);

#elif defined (DIRECT3D10)
#if defined(USE_VBIB_PUSH_DOWN)
	if (nFlags & FSL_READ)
	{	
		// in push down mode, return the original data ptr directly, instead of a staged buffer
		return pIB->RawData();
	}
#endif	
  int32 nDevSize = pDB->m_nSize;
  if (nSize && nSize < nDevSize)
    nDevSize = nSize;
  int nInds = nDevSize >> 1;
  // Get temporary index buffer
  DynamicIB<uint16> *pTempBuf = static_cast<DynamicIB<uint16> *>(m_pIBTemp[m_nCurStagedIB]);	
  pDB->m_bTempBuf = false;
  if (nInds > pTempBuf->GetCount())
  {		
    pDB->m_bTempBuf = true;
    pTempBuf = new DynamicIB <uint16>(gcpRendD3D->m_pd3dDevice, nInds, true);
		IF(!pTempBuf->GetInterface(), 0)
		{
			CHECK_HRESULT(hr);
			delete pTempBuf;
			return NULL;
		}
  }
  else
	{
		const uint32 nMaxIterations = m_nMaxStagingBuffers;
		uint32 i = 0;
		for(; i < nMaxIterations ; ++i )
		{		
			if (nInds+pTempBuf->GetOffset() > pTempBuf->GetCount())
			{				
				m_nCurStagedIB++;
				if (m_nCurStagedIB > m_nMaxStagingBuffers-1)
					m_nCurStagedIB = 0;
				pTempBuf = static_cast<DynamicIB<uint16> *>(m_pIBTemp[m_nCurStagedIB]);
			}
			else // found free buffer
				break;
		}
		// didn't found a valid buffer
		if( i == nMaxIterations )
		{			
			pDB->m_bTempBuf = true;
			pTempBuf = new DynamicIB <uint16>(gcpRendD3D->m_pd3dDevice, nInds, true);			
			IF(!pTempBuf->GetInterface(), 0)
			{
				delete pTempBuf;
				return NULL;
			}
		}
	}
  pDB->m_pStagedVB = pTempBuf;
  pDB->m_nLockFlags = nFlags;
  uint32 nOffsTemp = 0;
  if (nFlags & FSL_READ)
  {
    int nOffs = pTempBuf->GetOffset();
    if (nOffs + nInds > pTempBuf->GetCount())
      nOffs = 0;
    nOffs *= sizeof(uint16);
    D3D11_BOX box;
    ZeroStruct(box);
    box.left = nDevOffs;
    box.right = nDevOffs + nDevSize;
    box.bottom = 1;
    box.back = 1;
    gcpRendD3D->m_pd3dDeviceContext->CopySubresourceRegion(pTempBuf->GetInterface(), 0, nOffs, 0, 0, pIB, 0, &box);
  }
  pDst = (byte *)pTempBuf->LockIB(nInds, nOffsTemp, true,(nFlags & FSL_NONSTALL_MAP) != 0);
  pDB->m_nStagedOffset = nOffsTemp * sizeof(uint16);
  pDB->m_nDevOffset = nDevOffs;
  pDB->m_nStagedSize = nDevSize;
#endif
  CHECK_HRESULT(hr);
  return pDst;
}

void CDevBufferMan::UnlockIB(int32 nIB)
{
  int32 nDevOffs = 0;
  D3DIndexBuffer *pIB = GetD3DIB(nIB, &nDevOffs);
  assert(pIB);
  if (!pIB)
    return;

#if defined (DIRECT3D9)
  pIB->Unlock();
#elif defined (DIRECT3D10)
  SDevBuffer *pDB = GetDevIB(nIB);
  DynamicIB<uint16> *pStagedIB = static_cast<DynamicIB<uint16> *>(pDB->m_pStagedVB);
  assert(pStagedIB);
  if (!pStagedIB)
    return;
  pStagedIB->UnlockIB();
  if (pDB->m_nLockFlags & FSL_WRITE)
  {
    D3D11_BOX box;
    ZeroStruct(box);
    box.left = pDB->m_nStagedOffset;
    box.right = pDB->m_nStagedSize+box.left;
    box.bottom = 1;
    box.back = 1;
    gcpRendD3D->m_pd3dDeviceContext->CopySubresourceRegion(pIB, 0, pDB->m_nDevOffset, 0, 0, pStagedIB->GetInterface(), 0, &box);
  }
  if (pDB->m_bTempBuf)
  {		
    pDB->m_bTempBuf = false;
    SAFE_DELETE(pStagedIB);
  }
  pDB->m_pStagedVB = NULL;
#endif
}

//===============================================================================================================

int32 CDevBufferMan::CreateDevVB(int32 nSizeBuf, SDevBuffer& DB)
{
  DB.m_nSize = nSizeBuf;
  CDeviceManager *pDM = &gRenDev->m_DevMan;
  
	HRESULT hr = pDM->CreateBuffer(nSizeBuf, 1, 
			CDeviceManager::USAGE_CPU_WRITE|CDeviceManager::USAGE_CPU_CACHED_MEMORY, 
			CDeviceManager::BIND_VERTEX_BUFFER, (D3DBuffer**)&DB.m_D3DBuf.m_pVB);
  CHECK_HRESULT(hr);

  if (hr == S_OK)
	{
#   if BUFFER_ENABLE_DIRECT_ACCESS







#   endif
    return AllocateVBSlot(DB);
	}
  return -1;
}
int32 CDevBufferMan::CreateDevVBInPool(int32 nSizeBuf, SDevBuffer& DB)
{
  DB.m_nSize = nSizeBuf;
  DB.m_pPoolItem = new SDevPoolItem;
	int poolIdx=-1;
  HRESULT hr = S_OK;
  CDeviceManager *pDM = &gRenDev->m_DevMan;
  int32 nVB = -1;

#if !BUFFER_NO_LOCK_ON_UPDATE
	if(PoolAllocateChunkVB(nSizeBuf, DB.m_pPoolItem, poolIdx))
	{
		DB.m_pPoolItem->m_nPool = poolIdx;
		DB.m_D3DBuf.m_pVB = m_VBPools[poolIdx].m_D3DBuf.m_pVB;

		nVB = AllocateVBSlot(DB);

		ValidateVBPools();
	}
#else
	//pool alloc happens in UpdateVB
	nVB = AllocateVBSlot(DB);
#endif

  return nVB;
}

int32 CDevBufferMan::CreateDevIB(int32 nSizeBuf, SDevBuffer& DB)
{
  DB.m_nSize = nSizeBuf;
  CDeviceManager *pDM = &gRenDev->m_DevMan;

	HRESULT hr = pDM->CreateBuffer(nSizeBuf, 1, 
			CDeviceManager::USAGE_CPU_WRITE|CDeviceManager::USAGE_CPU_CACHED_MEMORY, 
			CDeviceManager::BIND_INDEX_BUFFER, (D3DBuffer**)&DB.m_D3DBuf.m_pVB);
  assert(hr == S_OK);

  if (hr == S_OK)
	{
#   if BUFFER_ENABLE_DIRECT_ACCESS







#   endif
		return AllocateIBSlot(DB);
	}
  return -1;
}
int32 CDevBufferMan::CreateDevIBInPool(int32 nSizeBuf, SDevBuffer& DB)
{
  DB.m_nSize = nSizeBuf;
  DB.m_pPoolItem = new SDevPoolItem;
  int poolIdx=-1;
  HRESULT hr = S_OK;
  CDeviceManager *pDM = &gRenDev->m_DevMan;
  int nIB = -1;
	
#if !BUFFER_NO_LOCK_ON_UPDATE
	if ( PoolAllocateChunkIB(nSizeBuf, DB.m_pPoolItem, poolIdx) )
	{
		DB.m_pPoolItem->m_nPool = poolIdx;
		DB.m_D3DBuf.m_pIB = m_IBPools[poolIdx].m_D3DBuf.m_pIB;
		nIB = AllocateIBSlot(DB);
	}
#else
	//pool alloc happens in UpdateIB
	nIB = AllocateIBSlot(DB);
#endif

  return nIB;
}

bool CDevBufferMan::ReleaseDevVB(SDevBuffer& DB)
{
#if defined (DIRECT3D9) || defined (DIRECT3D10)
	UnsetStreamSources(DB.m_D3DBuf.m_pVB);
#endif
  SAFE_RELEASE(DB.m_D3DBuf.m_pVB);
  return true;
}
bool CDevBufferMan::ReleaseDevVBInPool(SDevBuffer& DB)
{
  SDevPoolItem *pItem = DB.m_pPoolItem;

	//check the VB was ever allocated (BUFFER_NO_LOCK_ON_UPDATE)
	if(pItem->m_nPool!=-1)
	{
		SDevPool& Pool = m_VBPools[pItem->m_nPool];
		Pool.m_nAllocatedSize -= DB.m_nSize;
		bool bRes = Pool.m_AllocTree.Free(pItem->m_allocHdl);
		if (!bRes)
			iLog->Log("Error: CDevBufferMan::ReleaseDevVBInPool: chunk not found");

		if (!Pool.m_nAllocatedSize)
		{
#if defined (DIRECT3D9) || defined (DIRECT3D10)
			UnsetStreamSources(Pool.m_D3DBuf.m_pVB);
#endif
			SAFE_RELEASE(Pool.m_D3DBuf.m_pVB);
		}
	}

  SAFE_DELETE(DB.m_pPoolItem);

  return true;
}

bool CDevBufferMan::ReleaseDevIB(SDevBuffer& DB)
{
#if defined (DIRECT3D9) || defined (DIRECT3D10)
	UnsetIndices(DB.m_D3DBuf.m_pIB);
#endif
  SAFE_RELEASE(DB.m_D3DBuf.m_pIB);
  return true;
}
bool CDevBufferMan::ReleaseDevIBInPool(SDevBuffer& DB)
{
  SDevPoolItem *pItem = DB.m_pPoolItem;
	
	//check the IB was ever allocated (BUFFER_NO_LOCK_ON_UPDATE)
	if(pItem->m_nPool!=-1)
	{
		SDevPool& Pool = m_IBPools[pItem->m_nPool];
		Pool.m_nAllocatedSize -= DB.m_nSize;
		bool bRes = Pool.m_AllocTree.Free(pItem->m_allocHdl);
		if (!bRes)
			iLog->Log("Error: CDevBufferMan::ReleaseDevIBInPool: chunk not found");

		if (!Pool.m_nAllocatedSize)
		{
#if defined (DIRECT3D9) || defined (DIRECT3D10)
			UnsetIndices(Pool.m_D3DBuf.m_pIB);
#endif
			SAFE_RELEASE(Pool.m_D3DBuf.m_pIB);
		}
	}

  SAFE_DELETE(DB.m_pPoolItem);

  return true;
}

#if defined (DIRECT3D9) || defined (DIRECT3D10)
void CDevBufferMan::UnsetStreamSources(D3DVertexBuffer *pVB)
{
	if (!pVB)
	{
		return;
	}

	for (int i=0; i<MAX_STREAMS; i++)
	{
		if (gcpRendD3D->m_RP.m_VertexStreams[i].pStream == pVB)
		{
#if defined (DIRECT3D9)
			gcpRendD3D->m_pd3dDevice->SetStreamSource(i, NULL, 0, 0);
#elif defined (DIRECT3D10)
			ID3D11Buffer* pNullBuffer = NULL;
			UINT zeroStrideOffset = 0;
			gcpRendD3D->m_pd3dDeviceContext->IASetVertexBuffers(i, 1, &pNullBuffer, &zeroStrideOffset, &zeroStrideOffset);
#endif 
			gcpRendD3D->m_RP.m_VertexStreams[i].pStream = NULL;
		}
	}
}

void CDevBufferMan::UnsetIndices(D3DIndexBuffer *pIB)
{
	if (!pIB)
	{
		return;
	}

	if (gcpRendD3D->m_RP.m_pIndexStream == pIB)
	{
#if defined (DIRECT3D9)
		gcpRendD3D->m_pd3dDevice->SetIndices(NULL);
#elif defined (DIRECT3D10)
    gcpRendD3D->m_pd3dDeviceContext->IASetIndexBuffer(NULL, DXGI_FORMAT_R16_UINT, 0);
#endif
    gcpRendD3D->m_RP.m_pIndexStream = NULL;
	}
}
#endif

void CDevBufferMan::ValidateVBPools()
{
#ifdef _DEBUG
  uint32 i, j;

  for (i=0; i<m_VBPools.size(); i++)
  {
    SDevPool &Pool = m_VBPools[i];
    assert(Pool.m_nPoolSize == POOL_SIZE_VB);
    if (Pool.m_D3DBuf.m_pVB)
    {
      assert(Pool.m_nAllocatedSize != 0);
      int nSize = 0;
      for (j=0; j<m_VBs.size(); j++)
      {
        SDevBuffer &DB = m_VBs[j];
        if (DB.m_pPoolItem)
        {
          if (DB.m_pPoolItem->m_nPool == i)
            nSize += DB.m_nSize;
        }
        else
        {
          assert(DB.m_nSize == 0 || DB.m_nSize > POOL_SIZE_VB);
        }
      }
      assert (nSize == Pool.m_nAllocatedSize);
    }
    else
    {
      assert(Pool.m_nAllocatedSize == 0);
    }
  }
#endif
}

void CDevBufferMan::ValidateIBPools()
{
#ifdef _DEBUG
  uint32 i, j;

  for (i=0; i<m_IBPools.size(); i++)
  {
    SDevPool &Pool = m_IBPools[i];
    assert(Pool.m_nPoolSize == POOL_SIZE_IB);
    if (Pool.m_D3DBuf.m_pIB)
    {
      assert(Pool.m_nAllocatedSize != 0);
      int nSize = 0;
      for (j=0; j<m_IBs.size(); j++)
      {
        SDevBuffer &DB = m_IBs[j];
        if (DB.m_pPoolItem)
        {
          if (DB.m_pPoolItem->m_nPool == i)
            nSize += DB.m_nSize;
        }
        else
        {
          assert(DB.m_nSize == 0 || DB.m_nSize > POOL_SIZE_IB);
        }
      }
      assert (nSize == Pool.m_nAllocatedSize);
    }
    else
    {
      assert(Pool.m_nAllocatedSize == 0);
    }
  }
#endif
}

bool CDevBufferMan::AllocateChunk(int32 nSizeBuf, SDevPool& Pool, SDevPoolItem *pPoolItem)
{
  assert(nSizeBuf);

	DevBufferAllocHdl hdl = Pool.m_AllocTree.Allocate(nSizeBuf, NULL);
	if (hdl != InvalidDevBufferAllocHdl)
	{
		pPoolItem->m_allocHdl = hdl;
		pPoolItem->m_nBufOffset = (int32)Pool.m_AllocTree.Resolve(hdl);
		return true;
	}

  return false;
}

bool CDevBufferMan::PoolAllocateChunkIB(int32 nSizeBuf, SDevPoolItem *pPoolItem, int &poolIndex)
{
	uint32 i;
	HRESULT hr = S_OK;

	for (i=0; i<m_IBPools.size(); i++)
	{
		SDevPool &Pool = m_IBPools[i];
		if (AllocateChunk(nSizeBuf, Pool, pPoolItem))
		{
			assert(Pool.m_nPoolSize == POOL_SIZE_IB);
			if (!Pool.m_D3DBuf.m_pIB)
			{
				assert(Pool.m_nAllocatedSize == 0);

				hr = gRenDev->m_DevMan.CreateBuffer(POOL_SIZE_IB, 1, 
					CDeviceManager::USAGE_CPU_WRITE|CDeviceManager::USAGE_CPU_CACHED_MEMORY, 
					CDeviceManager::BIND_INDEX_BUFFER, (D3DBuffer**)&Pool.m_D3DBuf.m_pIB);
			
#if BUFFER_ENABLE_DIRECT_ACCESS
				if(hr==S_OK)
				{







				}
#endif
			}
			Pool.m_nAllocatedSize += nSizeBuf;
			break;
		}
	}

	if (i == m_IBPools.size())
	{
		SDevPool Pool(POOL_SIZE_IB);
		Pool.m_nAllocatedSize = nSizeBuf;

		hr = gRenDev->m_DevMan.CreateBuffer(POOL_SIZE_IB, 1, 
			CDeviceManager::USAGE_CPU_WRITE|CDeviceManager::USAGE_CPU_CACHED_MEMORY, 
			CDeviceManager::BIND_INDEX_BUFFER, (D3DBuffer**)&Pool.m_D3DBuf.m_pIB);
		
#if BUFFER_ENABLE_DIRECT_ACCESS
		if(hr==S_OK)
		{







		}
#endif

		m_IBPools.push_back(Pool);
		AllocateChunk(nSizeBuf, m_IBPools[i], pPoolItem);
	}

	if(hr==S_OK)
	{
		poolIndex = i;
		return true;
	}

	poolIndex = -1;
	return false;
}

bool CDevBufferMan::PoolAllocateChunkVB(int32 nSizeBuf, SDevPoolItem *pPoolItem, int &poolIndex)
{
	uint32 i;
	HRESULT hr = S_OK;

	for (i=0; i<m_VBPools.size(); i++)
	{
		SDevPool &Pool = m_VBPools[i];
		if (AllocateChunk(nSizeBuf, Pool, pPoolItem))
		{
			assert(Pool.m_nPoolSize == POOL_SIZE_VB);
			if (!Pool.m_D3DBuf.m_pVB)
			{
				assert(Pool.m_nAllocatedSize == 0);
				hr = gRenDev->m_DevMan.CreateBuffer(POOL_SIZE_VB, 1, 
					CDeviceManager::USAGE_CPU_WRITE|CDeviceManager::USAGE_CPU_CACHED_MEMORY, 
					CDeviceManager::BIND_VERTEX_BUFFER, (D3DBuffer**)&Pool.m_D3DBuf.m_pVB);
#if BUFFER_ENABLE_DIRECT_ACCESS
				if(hr==S_OK)
				{







				}
#endif
			}
			Pool.m_nAllocatedSize += hr==S_OK?nSizeBuf:0;
			break;
		}
	}

	if (i == m_VBPools.size())
	{
		SDevPool Pool(POOL_SIZE_VB);
		Pool.m_nAllocatedSize = nSizeBuf;
		hr = gRenDev->m_DevMan.CreateBuffer(POOL_SIZE_VB, 1, 
			CDeviceManager::USAGE_CPU_WRITE|CDeviceManager::USAGE_CPU_CACHED_MEMORY, 
			CDeviceManager::BIND_VERTEX_BUFFER, (D3DBuffer**)&Pool.m_D3DBuf.m_pVB);
		if(hr==S_OK)
		{
#if BUFFER_ENABLE_DIRECT_ACCESS







#endif
			m_VBPools.push_back(Pool);
			AllocateChunk(nSizeBuf, m_VBPools[i], pPoolItem);
		}
	}

	if(hr==S_OK)
	{
		poolIndex = i;
		return true;
	}

	poolIndex = -1;
	return false;
}

int32 CDevBufferMan::AllocateVBSlot(SDevBuffer& DB)
{
  int32 nVB = -1;
  if (m_FreeVBSlots.size())
  {
    nVB = m_FreeVBSlots[m_FreeVBSlots.size()-1];
    m_VBs[nVB] = DB;
    m_FreeVBSlots.pop_back();
  }
  else
  {
    nVB = m_VBs.size();
		LockDevMan();
    m_VBs.push_back(DB);
		UnlockDevMan();
  }
  ++m_activeVBs; 
  return nVB;
}

int32 CDevBufferMan::AllocateIBSlot(SDevBuffer& DB)
{
  int32 nIB = -1;
  if (m_FreeIBSlots.size())
  {
    nIB = m_FreeIBSlots[m_FreeIBSlots.size()-1];
    m_IBs[nIB] = DB;
    m_FreeIBSlots.pop_back();
  }
  else
  {
    nIB = m_IBs.size();
		LockDevMan();
    m_IBs.push_back(DB);
		UnlockDevMan();
  }
  ++m_activeIBs; 
  return nIB;
}

//==================================================================================================

void CDevBufferMan::ReleaseVBuffer(CVertexBuffer* pVB)
{
  SAFE_DELETE(pVB);
}

void CDevBufferMan::ReleaseIBuffer(CIndexBuffer* pIB)
{
  SAFE_DELETE(pIB);
}

CVertexBuffer *CDevBufferMan::CreateVBuffer(int32 nVerts, EVertexFormat eVF, const char *szName)
{
  CVertexBuffer *pVB = new CVertexBuffer(NULL, eVF);
  pVB->m_nVerts = nVerts;
  pVB->m_VS.m_nDevID = CreateVB(nVerts * CRenderMesh::m_cSizeVF[eVF]);

  return pVB;
}

CIndexBuffer *CDevBufferMan::CreateIBuffer(int32 nInds, const char *szName)
{
  CIndexBuffer *pIB = new CIndexBuffer(NULL);
  pIB->m_nInds = nInds;
  pIB->m_VS.m_nDevID = CreateIB(nInds * sizeof(uint16));

  return pIB;
}

void *CDevBufferMan::UpdateVBuffer(CVertexBuffer *pVB, void *pVerts, int32 nVerts)
{
  assert(pVB->m_VS.m_nDevID >= 0);
  void *pLocked = UpdateVB(pVB->m_VS.m_nDevID, pVerts, nVerts * CRenderMesh::m_cSizeVF[pVB->m_eVF], 0);

  return pLocked;
}

void *CDevBufferMan::UpdateIBuffer(CIndexBuffer *pIB, void *pInds, int32 nInds)
{
  assert(pIB->m_VS.m_nDevID >= 0);
  void *pLocked = UpdateIB(pIB->m_VS.m_nDevID, pInds, nInds * sizeof(uint16), 0);

  return pLocked;
}

bool CDevBufferMan::Cleanup()
{
  if (m_activeIBs!=0 || m_activeVBs!=0) 
    return false; 

#if BUFFER_NO_LOCK_ON_UPDATE
	std::vector<delayDeleteInfo>::iterator deleteIter = m_delayDeleteList.begin();
	while(deleteIter != m_delayDeleteList.end())
	{
		SDevPool& Pool = deleteIter->poolType == POOL_VERTEX ? m_VBPools[deleteIter->poolItem.m_nPool] : m_IBPools[deleteIter->poolItem.m_nPool];
		Pool.m_nAllocatedSize -= deleteIter->size;
		Pool.m_AllocTree.Free(deleteIter->poolItem.m_allocHdl);
		deleteIter = m_delayDeleteList.erase(deleteIter);
	}
	stl::free_container(m_delayDeleteList);
#endif

  stl::free_container(m_FreeIBSlots);
  stl::free_container(m_FreeVBSlots);
  stl::free_container(m_VBs);
  stl::free_container(m_IBs);
  stl::free_container(m_VBPools);
  stl::free_container(m_IBPools);
  return true; 
}

CVertexBuffer::~CVertexBuffer()
{
  if (m_VS.m_nDevID >= 0)
  {
		//STATIC UNINITIALISATION. This can be called after gRenDev has been released
		if(gRenDev)
		{
			gRenDev->m_DevBufMan.ReleaseVB(m_VS.m_nDevID);
		}
    m_VS.m_nDevID = -1;
  }
}

CIndexBuffer::~CIndexBuffer()
{
  if (m_VS.m_nDevID >= 0)
  {
    gRenDev->m_DevBufMan.ReleaseIB(m_VS.m_nDevID);
    m_VS.m_nDevID = -1;
  }
}

void CDevBufferAllocTree::Init(size_t capacity)
{
#ifndef _RELEASE
	if (capacity & (Alignment - 1))
		__debugbreak();
#endif

	m_capacity = capacity;
	m_available = capacity;

	m_chunks.clear();
	m_unusedChunks.clear();

	// Create sentinal chunks for the bucket lists
	for (size_t bucketIdx = 0; bucketIdx < NumBuckets; ++ bucketIdx)
	{
		DevBufferAllocChunk chunk = {0};
		chunk.busy = 1;
		chunk.freeNextIdx = bucketIdx;
		chunk.freePrevIdx = bucketIdx;

		m_freeBuckets[bucketIdx] = bucketIdx;
		m_chunks.push_back(chunk);
	}

	uint16 totalChunkIdx = AllocateChunk();
	DevBufferAllocChunk& totalChunk = m_chunks[totalChunkIdx];
	totalChunk.size = capacity;
	totalChunk.addrNextIdx = totalChunk.addrPrevIdx = InvalidChunkID;
	LinkFreeChunk(totalChunkIdx);
}

DevBufferAllocHdl CDevBufferAllocTree::Allocate(size_t sz, const char* source)
{
	if (sz > m_available)
		return InvalidDevBufferAllocHdl;

#ifndef _RELEASE
	if (sz > (1<<NumBuckets))
		__debugbreak();
#endif

	sz = Align(sz, Alignment);

	uint16 bestChunkIdx = InvalidChunkID;
	size_t bestWastage = (size_t)-1;

	for (size_t bucket = IntegerLog2(sz); (bestChunkIdx == InvalidChunkID) && (bucket < NumBuckets); ++ bucket)
	{
		size_t rootIdx = m_freeBuckets[bucket];
		DevBufferAllocChunk& root = m_chunks[rootIdx];

		for (size_t idx = root.freeNextIdx; idx != rootIdx; idx = m_chunks[idx].freeNextIdx)
		{
			DevBufferAllocChunk& chunk = m_chunks[idx];

#ifndef _RELEASE
			if (chunk.busy) __debugbreak();
			if (chunk.size < (1U<<bucket)) __debugbreak();
#endif

			if (chunk.size == sz)
			{
				bestChunkIdx = idx;
				bestWastage = 0;
				break;
			}

			if (chunk.size > sz)
			{
				uint32 wastage = chunk.size - sz;
				if (wastage < bestWastage)
				{
					bestChunkIdx = idx;
					bestWastage = wastage;
				}
			}
		}
	}

	if (bestChunkIdx != InvalidChunkID)
	{
		DevBufferAllocChunk* bestChunk = &m_chunks[bestChunkIdx];

#ifndef _RELEASE
		bestChunk->source = source;
#endif

		UnlinkFreeChunk(bestChunkIdx);

		if (bestChunk->size == sz)
		{
			// Perfect fit.
		}
		else
		{
			// Split
			uint16 splitChunkIdx = AllocateChunk();
			bestChunk = &m_chunks[bestChunkIdx];

			DevBufferAllocChunk& splitChunk = m_chunks[splitChunkIdx];

			splitChunk.busy = 0;
			splitChunk.size = bestWastage;
			splitChunk.ptr = bestChunk->ptr + sz;
#ifndef _RELEASE
			splitChunk.source = "";
#endif

			splitChunk.addrNextIdx = bestChunk->addrNextIdx;
			splitChunk.addrPrevIdx = bestChunkIdx;
			bestChunk->addrNextIdx = splitChunkIdx;
			if (splitChunk.addrNextIdx != InvalidChunkID)
				m_chunks[splitChunk.addrNextIdx].addrPrevIdx = splitChunkIdx;

			bestChunk->size = sz;

#ifndef _RELEASE
			if (splitChunk.addrPrevIdx != InvalidChunkID)
				if (m_chunks[splitChunk.addrPrevIdx].ptr + m_chunks[splitChunk.addrPrevIdx].size != splitChunk.ptr)
					__debugbreak();
			if (splitChunk.addrNextIdx != InvalidChunkID)
				if (splitChunk.ptr + splitChunk.size != m_chunks[splitChunk.addrNextIdx].ptr)
					__debugbreak();
#endif

			LinkFreeChunk(splitChunkIdx);
		}

		bestChunk->busy = 1;
		m_available -= sz;

#ifndef _RELEASE
		// m_available is unsigned, so check for underflow
		if (m_available > m_capacity) __debugbreak();
#endif
	}

	return bestChunkIdx;
}

bool CDevBufferAllocTree::Free(DevBufferAllocHdl hdl)
{
	if (hdl != InvalidChunkID)
	{
		DevBufferAllocChunk* chunk = &m_chunks[hdl];
		chunk->busy = 0;
		m_available += chunk->size;

#ifndef _RELEASE
		if (m_available > m_capacity) __debugbreak();
#endif

		LinkFreeChunk(hdl);

		uint16 prevChunkIdx = chunk->addrPrevIdx;
		if (prevChunkIdx != InvalidChunkID)
		{
			DevBufferAllocChunk& prevChunk = m_chunks[prevChunkIdx];
			if (!prevChunk.busy)
			{
				// Merge with previous chunk

				UnlinkFreeChunk(hdl);
				UnlinkFreeChunk(prevChunkIdx);

				prevChunk.size += chunk->size;
				prevChunk.addrNextIdx = chunk->addrNextIdx;
				if (chunk->addrNextIdx != InvalidChunkID)
					m_chunks[chunk->addrNextIdx].addrPrevIdx = prevChunkIdx;

#ifndef _RELEASE
				if (prevChunk.addrPrevIdx != InvalidChunkID)
					if (m_chunks[prevChunk.addrPrevIdx].ptr + m_chunks[prevChunk.addrPrevIdx].size != prevChunk.ptr)
						__debugbreak();
				if (prevChunk.addrNextIdx != InvalidChunkID)
					if (prevChunk.ptr + prevChunk.size != m_chunks[prevChunk.addrNextIdx].ptr)
						__debugbreak();
#endif

				LinkFreeChunk(prevChunkIdx);
				ReleaseChunk(hdl);

				// Pretend the released chunk is the previous one, so the next merge can happen

				hdl = prevChunkIdx;
				chunk = &prevChunk;
			}
		}

		uint16 nextChunkIdx = chunk->addrNextIdx;
		if (nextChunkIdx != InvalidChunkID)
		{
			DevBufferAllocChunk& nextChunk = m_chunks[nextChunkIdx];
			if (!nextChunk.busy)
			{
				// Merge with next chunk

				UnlinkFreeChunk(hdl);
				UnlinkFreeChunk(nextChunkIdx);

				chunk->size += nextChunk.size;
				chunk->addrNextIdx = nextChunk.addrNextIdx;
				if (chunk->addrNextIdx != InvalidChunkID)
					m_chunks[chunk->addrNextIdx].addrPrevIdx = hdl;

#ifndef _RELEASE
				if (chunk->addrPrevIdx != InvalidChunkID)
					if (m_chunks[chunk->addrPrevIdx].ptr + m_chunks[chunk->addrPrevIdx].size != chunk->ptr)
						__debugbreak();
				if (chunk->addrNextIdx != InvalidChunkID)
					if (chunk->ptr + chunk->size != m_chunks[chunk->addrNextIdx].ptr)
						__debugbreak();
#endif

				LinkFreeChunk(hdl);
				ReleaseChunk(nextChunkIdx);
			}
		}

		return true;
	}

	return false;
}

uint16 CDevBufferAllocTree::AllocateChunk()
{
	if (!m_unusedChunks.empty())
	{
		uint16 result = m_unusedChunks.back();
		m_unusedChunks.pop_back();
		return result;
	}

	m_chunks.push_back(DevBufferAllocChunk());
	return (uint16) (m_chunks.size() - 1);
}

void CDevBufferAllocTree::ReleaseChunk(uint16 id)
{
	m_unusedChunks.push_back(id);
}

void CDevBufferAllocTree::LinkFreeChunk(uint16 id)
{
	DevBufferAllocChunk& chunk = m_chunks[id];

#ifndef _RELEASE
	if (chunk.busy) __debugbreak();
#endif

	int bucket = IntegerLog2(chunk.size);

	size_t rootIdx = m_freeBuckets[bucket];
	DevBufferAllocChunk& root = m_chunks[rootIdx];

	chunk.freeNextIdx = root.freeNextIdx;
	chunk.freePrevIdx = rootIdx;
	m_chunks[chunk.freeNextIdx].freePrevIdx = id;
	root.freeNextIdx = id;
}

//////////////////////////////////////////////////////////////////////////

#if defined(DIRECT3D10) && !defined(PS3) && !defined(CAFE)
WrappedDX11Buffer::WrappedDX11Buffer(const WrappedDX11Buffer& src)
{
	m_pBuffer = src.m_pBuffer;
	if (m_pBuffer)
		m_pBuffer->AddRef();

	m_pSRV = src.m_pSRV;
	if (m_pSRV)
		m_pSRV->AddRef();

	m_numElements = src.m_numElements;
}

WrappedDX11Buffer& WrappedDX11Buffer::operator=(const WrappedDX11Buffer& rhs)
{
	ID3D11Buffer* pOldBuffer = m_pBuffer;
	ID3D11ShaderResourceView* pOldSRV = m_pSRV;

	m_pBuffer = rhs.m_pBuffer;
	if (m_pBuffer)
		m_pBuffer->AddRef();

	m_pSRV = rhs.m_pSRV;
	if (m_pSRV)
		m_pSRV->AddRef();

	SAFE_RELEASE(pOldSRV);
	SAFE_RELEASE(pOldBuffer);

	m_numElements = rhs.m_numElements;

	return *this;
}

WrappedDX11Buffer::~WrappedDX11Buffer()
{
	SAFE_RELEASE(m_pSRV);
	SAFE_RELEASE(m_pBuffer);
}

void WrappedDX11Buffer::Create(unsigned int numElements, unsigned int elementSize, DXGI_FORMAT elementFormat, const void* pData, D3D11_BIND_FLAG bindFlag)
{
	SAFE_RELEASE(m_pSRV);
	SAFE_RELEASE(m_pBuffer);

	D3D11_BUFFER_DESC Desc;
	Desc.BindFlags = bindFlag;
	Desc.ByteWidth = numElements * elementSize;
	Desc.CPUAccessFlags = 0;
	Desc.MiscFlags = 0;
	Desc.StructureByteStride = elementSize;
	Desc.Usage = D3D11_USAGE_IMMUTABLE;
	D3D11_SUBRESOURCE_DATA Data;
	Data.pSysMem = pData;
	Data.SysMemPitch = Desc.ByteWidth;
	Data.SysMemSlicePitch = Desc.ByteWidth;
	gcpRendD3D->m_pd3dDevice->CreateBuffer(&Desc, &Data, &m_pBuffer);

	if (bindFlag & D3D11_BIND_SHADER_RESOURCE)
	{
		D3D11_SHADER_RESOURCE_VIEW_DESC SRVDesc;
		SRVDesc.Format = elementFormat;
		SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER;
		SRVDesc.Buffer.ElementOffset = 0;
		SRVDesc.Buffer.ElementWidth = numElements;
		gcpRendD3D->m_pd3dDevice->CreateShaderResourceView(m_pBuffer, &SRVDesc, &m_pSRV);
	}

	m_numElements = numElements;
}
#endif // #if defined(DIRECT3D10) && !defined(PS3)
